﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Nvigorate.Data.Query;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;
using Nvigorate.TestObjects;
using Nvigorate.Test.Extensions;
namespace Nvigorate.Test.Relational.Repository
{
    [TestClass]
    public class PersistenceKeyManagerTests
    {
        public PersistenceKeyManagerTests()
        {
            MapFake.PopulateMaps();
        }

        #region RegisterType Tests

        [TestMethod]
        public void RegisterType_AddingTypeWithPrimaryKey_AddsPrimaryKeyToKeyManager()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof (Person)).Sources["Person"];

            // Act
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            var keys = keyManager.GetKeyList(typeof(Person), sourceMap);

            // Assert
            Assert.AreEqual(1, keys.Count());
            Assert.AreEqual("SocialSecurity", keys.First());
        }

        [TestMethod]
        public void RegisterType_AddingTypeWithPrimaryKey_CreatesTableVariableForType()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Person)).Sources["Person"];

            // Act
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            var tables = keyManager.GetTableDeclarations() as QueryList;
            
            // Assert
            Assert.IsNotNull(tables);
            var table = tables.Where(t => ((TableVariable) t).Table.Name == "Person").FirstOrDefault() as TableVariable;
            Assert.IsNotNull(table);
            Assert.AreEqual(2, table.ColumnList.Count);
            Assert.IsTrue(table.ColumnList.Any(t => ((SchemaColumn)t).Name == "PersistenceTicket"),
                "PersistenceTicket is not in table variable.");
            Assert.IsTrue(table.ColumnList.Any(t => ((SchemaColumn)t).Name == "SocialSecurity"),
                "SocialSecurity is not in table variable.");
        }

        [TestMethod]
        public void RegisterType_AddingTypeWithForeignKey_AddsPrimaryAndForeignKeyToKeyManager()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof (Vehicle)).Sources["Vehicle"];

            // Act
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof (Person));
            keyManager.RegisterType(typeof (Vehicle));
            var keys = keyManager.GetKeyList(typeof (Vehicle), sourceMap);

            // Assert
            Assert.AreEqual(2, keys.Count());
            Assert.IsTrue(keys.Any(k => k == "VIN"), "VIN is not in keys");
            Assert.IsTrue(keys.Any(k => k == "OwnerSocial"), "OwnerSocial is not in keys.");
        }

        [TestMethod]
        public void RegisterType_AddingTypeWithPrimaryKeyAndForeignKey_CreatesTableVariableForTypeWithBothColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Vehicle)).Sources["Vehicle"];

            // Act
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var tables = keyManager.GetTableDeclarations() as QueryList;

            // Assert
            Assert.IsNotNull(tables);
            var table = tables.Where(t => ((TableVariable) t).Table.Name == "Vehicle").FirstOrDefault() as TableVariable;
            Assert.IsNotNull(table);
            Assert.AreEqual(3, table.ColumnList.Count);
            Assert.IsTrue(table.ColumnList.Any(t => ((SchemaColumn)t).Name == "PersistenceTicket"), 
                "PersistenceTicket is not in table variable.");
            Assert.IsTrue(table.ColumnList.Any(t => ((SchemaColumn)t).Name == "VIN"), "VIN column is not in table variable.");
            Assert.IsTrue(table.ColumnList.Any(t => ((SchemaColumn)t).Name == "OwnerSocial"), "OwnerSocial column is not in table variable.");
        }
        #endregion

        #region GetKeyVariable Tests

        [TestMethod]
        public void GetKeyVariable_UsingPrimaryKey_ReturnsNull()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof (Vehicle));
            var assignment = sourceMap.GetAssignmentsByPredicate(a => a.Target.Field == "VIN").FirstOrDefault();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            // Act
            var keyVariable = keyManager.GetKeyVariable(typeof (Vehicle), assignment);

            // Assert
            Assert.IsNull(keyVariable);
        }

        [TestMethod]
        public void GetKeyVariable_UsingForeignKey_ReturnsKeyVariableName()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Vehicle));
            var assignment = sourceMap.GetAssignmentsByPredicate(a => a.Target.Field == "OwnerSocial").FirstOrDefault();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            // Act
            var keyVariable = keyManager.GetKeyVariable(typeof(Vehicle), assignment);

            // Assert
            Assert.AreEqual("key1", keyVariable);
        }

        [TestMethod]
        public void GetKeyVariable_UsingNonKey_ReturnsNull()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Vehicle));
            var assignment = sourceMap.GetAssignmentsByPredicate(a => a.Target.Field == "Model").FirstOrDefault();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            // Act
            var keyVariable = keyManager.GetKeyVariable(typeof(Vehicle), assignment);

            // Assert
            Assert.IsNull(keyVariable);
        }

        [TestMethod]
        [Ignore] // until the problem with manufacturer is corrected this test fails due to a missing map
        public void GetKeyVariable_UsingMultipleKeys_ReturnsUniqueVariableNames()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Vehicle));
            var personAssignment = sourceMap.GetAssignmentsByPredicate(a => a.Target.Field == "OwnerSocial").FirstOrDefault();
            var mfgAssignment =
                sourceMap.GetAssignmentsByPredicate(a => a.Target.Field == "ManufacturerId").FirstOrDefault();

            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Manufacturer));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            // Act
            var personKeyVariable = keyManager.GetKeyVariable(typeof(Vehicle), personAssignment);
            var mfgKeyVariable = keyManager.GetKeyVariable(typeof (Vehicle), mfgAssignment);

            // Assert
            Assert.AreNotEqual(personKeyVariable, mfgKeyVariable);      
        }
        #endregion

        #region GetInsertIntoKeyTable Tests

        [TestMethod]
        public void GetInsertIntoKeyTable_SinglePrimaryKey_CreatesInsertForTempTableWithTwoColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var person = new Person
                               {
                                   SocialSecurity = "123456789",
                                   FirstName = "First",
                                   LastName = "Last"
                               };

            var persistable = PersistenceDecorator.Create(person);
            var insert = keyManager.GetInsertIntoKeyTable(persistable) as Insert;

            // Assert
            Assert.IsNotNull(insert);
            Assert.AreEqual("Person", insert.Table.Name);
            Assert.IsTrue(insert.Table.IsVariable);
            Assert.AreEqual(2, insert.ColumnList.Count);

            QueryAssert.InsertColumnValue(insert, "PersistenceTicket", persistable.PersistenceTicket);
            QueryAssert.InsertColumnValue(insert, "SocialSecurity", person.SocialSecurity);
        }

        [TestMethod]
        public void GetInsertIntoKeyTable_PrimaryKeyAndForeignKey_CreatesInsertForTempTableWithThreeColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var vehicle = new Vehicle
                               {
                                   VIN = "12345",
                                   OwnerSocial = "123456789",
                                   Model = "Accord"
                               };

            var persistable = PersistenceDecorator.Create(vehicle);
            var insert = keyManager.GetInsertIntoKeyTable(persistable) as Insert;

            // Assert
            Assert.IsNotNull(insert);
            Assert.AreEqual("Vehicle", insert.Table.Name);
            Assert.IsTrue(insert.Table.IsVariable);
            Assert.AreEqual(3, insert.ColumnList.Count);

            QueryAssert.InsertColumnValue(insert, "PersistenceTicket", persistable.PersistenceTicket);
            QueryAssert.InsertColumnValue(insert, "VIN", vehicle.VIN);
            QueryAssert.InsertColumnValue(insert, "OwnerSocial", vehicle.OwnerSocial);
        }

        [TestMethod]
        public void GetInsertIntoKeyTable_PrimaryKeyAndTwoForeignKeys_CreatesInsertForTempTableWithFourColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var vehicle = new Vehicle
                               {
                                   VIN = "12345",
                                   OwnerSocial = "123456789",
                                   Model = "Accord",
                                   ManufacturerId = 5
                               };
            var persistable = PersistenceDecorator.Create(vehicle);
            var insert = keyManager.GetInsertIntoKeyTable(persistable) as Insert;

            // Assert
            Assert.IsNotNull(insert);
            Assert.AreEqual("Vehicle", insert.Table.Name);
            Assert.IsTrue(insert.Table.IsVariable);
            Assert.AreEqual(3, insert.ColumnList.Count);
            
            QueryAssert.InsertColumnValue(insert, "PersistenceTicket", persistable.PersistenceTicket);
            QueryAssert.InsertColumnValue(insert, "VIN", vehicle.VIN);
            QueryAssert.InsertColumnValue(insert, "OwnerSocial", vehicle.OwnerSocial);
        }

        #endregion

        #region GetOutputClauseForSource Tests

        [TestMethod]
        public void GetOutputClauseForSource_WithOnlyPrimaryKey_ReturnsTwoColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof (Person)).GetSourceMapsInCreateOrder(false).FirstOrDefault();

            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            var person = new Person {SocialSecurity = "123456789"};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var persistable = PersistenceDecorator.Create(person);
            var outputs = keyManager.GetOutputClauseForSource(persistable, sourceMap, "");

            // Assert
            Assert.AreEqual(persistable.PersistenceTicket.Quoted(), ((Reference)outputs.ColumnList[0]).Value);
            Assert.AreEqual("SocialSecurity", ((Column)outputs.ColumnList[1]).Name);
            Assert.AreEqual("Person", outputs.Table.Name);
            Assert.IsTrue(outputs.Table.IsVariable);
        }

        [TestMethod]
        public void GetOutputClauseForSource_WithPrimaryAndForeignKey_ReturnsThreeColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Vehicle)).GetSourceMapsInCreateOrder(false).FirstOrDefault();

            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var vehicle = new Vehicle
                              {
                                  VIN = "12345",
                                  OwnerSocial = "123456789",
                                  Model = "Accord"
                              };
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var persistable = PersistenceDecorator.Create(vehicle);
            var outputs = keyManager.GetOutputClauseForSource(persistable, sourceMap, "");

            // Assert
            Assert.AreEqual(persistable.PersistenceTicket.Quoted(), ((Reference)outputs.ColumnList[0]).Value);
            Assert.AreEqual("vehicleId", ((Column)outputs.ColumnList[1]).Name);
            Assert.AreEqual("ownerSSN", ((Column)outputs.ColumnList[2]).Name);
            Assert.AreEqual("Vehicle", outputs.Table.Name);
            Assert.IsTrue(outputs.Table.IsVariable);
        }

        [TestMethod]
        public void GetOutputClauseForSource_WithPrimaryAndTwoForeignKeys_ReturnsFourColumns()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var sourceMap = maps.GetMap(typeof(Vehicle)).GetSourceMapsInCreateOrder(false).FirstOrDefault();

            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));

            var vehicle = new Vehicle
            {
                VIN = "12345",
                OwnerSocial = "123456789",
                Model = "Accord"
            };
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var persistable = PersistenceDecorator.Create(vehicle);
            var outputs = keyManager.GetOutputClauseForSource(persistable, sourceMap, "");

            // Assert
            Assert.AreEqual(persistable.PersistenceTicket.Quoted(), ((Reference)outputs.ColumnList[0]).Value);
            Assert.AreEqual("vehicleId", ((Column)outputs.ColumnList[1]).Name);
            Assert.AreEqual("ownerSSN", ((Column)outputs.ColumnList[2]).Name);
            Assert.AreEqual("Vehicle", outputs.Table.Name);
            Assert.IsTrue(outputs.Table.IsVariable);
        }


        #endregion

        #region GetVariableAssignments Tests

        [TestMethod]
        public void GetVariableAssignments_WhenKeyExists_ReturnsVariableAssignmentThatGetsValueFromTempTable()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);
            var sourceMap = maps.GetMap(typeof (Vehicle)).GetSourceMapsInCreateOrder(false).First();

            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();

            // Act
            var person = new Person {SocialSecurity = "123456789"};
            var persistable = PersistenceDecorator.Create(person);
            var assignments = keyManager.GetVariableAssignments(typeof (Vehicle), sourceMap, persistable);

            // Assert
            Assert.AreEqual(1, assignments.Count);
            var assignment = assignments[0] as Variable;
            Assert.IsNotNull(assignment);
            Assert.AreEqual("key1", assignment.Name);

            var select = assignment.Value as Select;
            Assert.IsNotNull(select);
            Assert.AreEqual("Person", select.Table.Name);
            Assert.AreEqual("SocialSecurity", ((Column)select.ColumnList[0]).Name);
            Assert.AreEqual(1, ((Reference)select.TopClause.Count).Value);
            Assert.AreEqual(1, select.Qualifiers.Count);

            var where = select.Qualifiers[0] as Where;
            Assert.IsNotNull(where);
            Assert.AreEqual(2, where.SearchCriteria[0].Criteria.Count);

            QueryAssert.EqualCriterion(where.SearchCriteria[0].Criteria[0], "PersistenceTicket",
                                       persistable.PersistenceTicket.Quoted());
            QueryAssert.NotEqualToNullCriterion(where.SearchCriteria[0].Criteria[1], "SocialSecurity");
        }

        #endregion

        #region GetVariableDeclarations Tests

        [TestMethod]
        public void GetVariableDeclarations_WhenTypeHasPrimaryKey_VariableDeclarationIsProvided()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);
            keyManager.RegisterType(typeof(Person));

            // Act
            var declarations = keyManager.GetVariableDeclarations() as QueryList;

            // Assert
            Assert.AreEqual(1, declarations.Count);
            Assert.IsNotNull(((Variable)declarations[0]).VariableType);
        }

        [TestMethod]
        public void GetVariableDeclarations_WhenTypeHasPrimaryAndForeignKey_TwoDeclarationsAreProvidedForChildAndOneForParent()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));

            // Act
            var declarations = keyManager.GetVariableDeclarations() as QueryList;

            // Assert
            Assert.AreEqual(3, declarations.Count);
            Assert.IsNotNull(((Variable)declarations[1]).VariableType);            
        }
        #endregion

        #region GetTableVariables Test
        [TestMethod]
        public void GetTableVariables_WhenOneTypeRegistered_ReturnsOneTable()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));

            // Act
            var tables = keyManager.GetTableDeclarations() as QueryList;

            // Assert
            Assert.AreEqual(1, tables.Count);
            Assert.AreEqual("Person", ((TableVariable)tables[0]).Table.Name);
        }

        [TestMethod]
        public void GetTableVariables_WhenTwoTypesRegistered_ReturnsTwoTables()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));

            // Act
            var tables = keyManager.GetTableDeclarations() as QueryList;

            // Assert
            Assert.AreEqual(2, tables.Count);
            Assert.AreEqual("Person", ((TableVariable)tables[0]).Table.Name);
            Assert.AreEqual("Vehicle", ((TableVariable)tables[1]).Table.Name);
        }

        #endregion

        #region GetTableVariableNames Tests

        [TestMethod]
        public void GetTableVariableNames_WhenOneTypeRegistered_ReturnsOneTable()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));

            // Act
            var tables = keyManager.GetTableVariableNames().ToList();

            // Assert
            Assert.AreEqual(1, tables.Count);
            Assert.AreEqual("Person", tables[0]);
        }

        [TestMethod]
        public void GetTableVariableNames_WhenTwoTypesRegistered_ReturnsTwoTables()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));

            // Act
            var tables = keyManager.GetTableVariableNames().ToList()  ;

            // Assert
            Assert.AreEqual(2, tables.Count);
            Assert.AreEqual("Person", tables[0]);
            Assert.AreEqual("Vehicle", tables[1]);
        }
        #endregion

        #region GetTableSelection Tests

        [TestMethod]
        public void GetTableSelection_WhenOneTypeRegistered_ReturnsSelectUsingMaxGroupedByTicket()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));

            // Act
            var selects = keyManager.GetTableSelection() as QueryList;

            // Assert
            Assert.AreEqual(1, selects.Count);
            var select = selects[0] as Select;
            Assert.IsNotNull(select);
            Assert.AreEqual("Person", select.Table.Name);
            Assert.AreEqual(2, select.ColumnList.Count);

            QueryAssert.MaxFunction(select.ColumnList[0], "PersistenceTicket");
            QueryAssert.MaxFunction(select.ColumnList[1], "SocialSecurity");

            Assert.AreEqual(1, select.Qualifiers.Count);
            var group = select.Qualifiers[0] as Group;
            Assert.AreEqual("PersistenceTicket", ((Column)group.ColumnList[0]).Name);
        }

        [TestMethod]
        public void GetTableSelection_WhenTwoTypesRegistered_ReturnsTwoSelectQueries()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var keyManager = new PersistenceKeyManager(maps);

            keyManager.RegisterType(typeof(Person));
            keyManager.RegisterType(typeof(Vehicle));

            // Act
            var selects = keyManager.GetTableSelection() as QueryList;

            // Assert
            Assert.AreEqual(2, selects.Count);
        }
        #endregion

        #region Private Methods

        private IRepositoryMapIndex CreateRepositoryMapIndex()
        {
            return new RepositoryMapIndex(new MapIndex(), "simple");
        }

        private ISourceMap GetSourceMap(IRepositoryMapIndex repositoryMapIndex, Type type)
        {
            return repositoryMapIndex.GetMap(type).GetSourceMapsInCreateOrder(false).First();
        }
        #endregion
    }
}
 